import React, { useState, useEffect, useRef, useCallback } from 'react';
import { createRoot } from 'react-dom/client';
import Chart from 'chart.js/auto';
import { icons } from 'lucide-react';

/**
 * Renders an icon from the Lucide icon library.
 * This version uses the 'lucide-react' library for better integration with React.
 * @param {object} props - Component props.
 * @param {string} props.name - The name of the icon.
 * @param {string} [props.className] - Additional CSS classes.
 * @param {number} [props.size=16] - The size of the icon.
 */
const Icon = ({ name, className, size = 16 }) => {
    const LucideIcon = icons[name];
    if (!LucideIcon) {
        // Fallback for missing icons
        return (
            <span className={className} title={`Icon ${name} not found`}>
                <svg width={size} height={size} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
                    <circle cx="12" cy="12" r="10"></circle>
                    <path d="M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3"></path>
                    <path d="M12 17h.01"></path>
                </svg>
            </span>
        );
    }
    return React.createElement(LucideIcon, { className, size });
};


/**
 * A debounced input component to prevent rapid state updates on every keystroke.
 * @param {object} props - Component props.
 * @param {string} props.value - The current value of the input.
 * @param {function} props.onChange - Callback function to call with the new value after delay.
 * @param {string} props.placeholder - Placeholder text for the input.
 * @param {number} [props.delay=300] - The debounce delay in milliseconds.
 */
const DebouncedInput = ({ value, onChange, placeholder, delay = 300 }) => {
    const [displayValue, setDisplayValue] = useState(value);
    const timeoutRef = useRef(null);

    const handleChange = (e) => {
        const { value: nextValue } = e.target;
        setDisplayValue(nextValue);
        clearTimeout(timeoutRef.current);
        timeoutRef.current = setTimeout(() => {
            onChange(nextValue);
        }, delay);
    };

    useEffect(() => {
        setDisplayValue(value);
    }, [value]);

    return <input type="text" value={displayValue} onChange={handleChange} placeholder={placeholder} className="w-full bg-slate-700 border border-slate-600 rounded-md px-3 py-1.5 text-sm text-white focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500" />;
};


/**
 * Main application component.
 */
const App = () => {
    // --- State Management ---
    const [stats, setStats] = useState({ track_count: 0 });
    const [isLoading, setIsLoading] = useState(false);
    const [toolStatus, setToolStatus] = useState('');
    const [error, setError] = useState(null);
    const [activeTab, setActiveTab] = useState('recognize');

    // State for lists and search (from new.js)
    const [allSongs, setAllSongs] = useState([]);
    const [filteredSongs, setFilteredSongs] = useState([]);
    const [songSearch, setSongSearch] = useState('');
    const [allSamples, setAllSamples] = useState([]);
    const [filteredSamples, setFilteredSamples] = useState([]);
    const [sampleSearch, setSampleSearch] = useState('');

    // State for results and modals
    const [recognitionResult, setRecognitionResult] = useState(null);
    const [latestBatchResult, setLatestBatchResult] = useState(null);
    const [selectedSong, setSelectedSong] = useState(null);
    const [diagnosisResult, setDiagnosisResult] = useState(null);
    const [comparisonDetails, setComparisonDetails] = useState(null);

    // --- Data Fetching ---
    const fetchInitialData = useCallback(() => {
        setIsLoading(true);
        Promise.all([
            fetch('/api/stats').then(res => res.json()),
            fetch('/api/songs').then(res => res.json()),
            fetch('/api/tools/list_samples').then(res => res.json()),
            fetch('/api/batch_results/latest').then(res => res.ok ? res.json() : null)
        ]).then(([statsData, songsData, samplesData, batchData]) => {
            setStats(statsData);
            setAllSongs(songsData);
            setFilteredSongs(songsData);
            setAllSamples(samplesData);
            setFilteredSamples(samplesData);
            if (batchData) setLatestBatchResult(batchData);
        }).catch(err => {
            console.error(err);
            setError('无法加载初始数据，请检查后端服务是否运行。');
        }).finally(() => setIsLoading(false));
    }, []);

    useEffect(fetchInitialData, [fetchInitialData]);

    // --- Search/Filter Logic (from new.js) ---
    useEffect(() => {
        const lowerCaseQuery = songSearch.toLowerCase();
        setFilteredSongs(allSongs.filter(song => song.name.toLowerCase().includes(lowerCaseQuery) || song.artist?.toLowerCase().includes(lowerCaseQuery)));
    }, [songSearch, allSongs]);

    useEffect(() => {
        const lowerCaseQuery = sampleSearch.toLowerCase();
        setFilteredSamples(allSamples.filter(sample => sample.toLowerCase().includes(lowerCaseQuery)));
    }, [sampleSearch, allSamples]);


    // --- Event Handlers ---
    const handleRecognize = async (file, filepath) => {
        setIsLoading(true);
        setError(null);
        setRecognitionResult(null);
        let body;
        const headers = {};
        if (file) {
            body = new FormData();
            body.append('audio', file);
        } else {
            body = JSON.stringify({ filepath });
            headers['Content-Type'] = 'application/json';
        }
        try {
            const response = await fetch('/api/recognize', { method: 'POST', headers, body });
            const data = await response.json();
            if (response.ok) {
                setRecognitionResult(data);
            } else {
                setError(data.error || '识别过程中发生未知错误。');
            }
        } catch (err) {
            console.error(err);
            setError('无法连接到识别服务，请检查网络和后端状态。');
        } finally {
            setIsLoading(false);
        }
    };

    const handleGenerateSamples = async (noiseConfig) => {
        setToolStatus('正在生成样本...');
        try {
            const response = await fetch('/api/tools/generate_samples', {
                method: 'POST',
                headers: { 'Content-Type': 'application/json' },
                body: JSON.stringify(noiseConfig)
            });
            const data = await response.json();
            setToolStatus(data.message || '操作完成。');
            fetchInitialData();
        } catch (err) {
            console.error(err);
            setToolStatus('生成失败。');
        } finally {
            setTimeout(() => setToolStatus(''), 3000);
        }
    };

    const handleRunBatchTest = async (customConfig = null) => {
        setToolStatus('批量测试已在后台启动...');
        setLatestBatchResult(null);
        try {
            const response = await fetch('/api/tools/run_batch_test', {
                method: 'POST',
                headers: { 'Content-Type': 'application/json' },
                body: customConfig ? JSON.stringify(customConfig) : JSON.stringify({}),
            });
            const data = await response.json();
            setToolStatus(data.message || '启动成功。');
            pollForBatchResults();
        } catch (err) {
            console.error(err);
            setToolStatus('启动批量测试失败。');
        }
    };

    const pollForBatchResults = () => {
        const intervalId = setInterval(async () => {
            try {
                const response = await fetch('/api/batch_results/latest');
                if (response.ok) {
                    const data = await response.json();
                    if (data && !data.error) {
                        setLatestBatchResult(data);
                        setToolStatus('批量测试完成！');
                        clearInterval(intervalId);
                        setTimeout(() => setToolStatus(''), 5000);
                    }
                }
            } catch (error) {
                console.error("轮询错误:", error);
            }
        }, 5000);
    };

    const handleDiagnose = async (sampleName) => {
        setToolStatus(`正在诊断 ${sampleName}...`);
        setDiagnosisResult({ isLoading: true });
        try {
            const response = await fetch('/api/tools/diagnose', {
                method: 'POST',
                headers: { 'Content-Type': 'application/json' },
                body: JSON.stringify({ sample_name: sampleName }),
            });
            const data = await response.json();
            setDiagnosisResult(data);
        } catch (err) {
            console.error(err);
            setDiagnosisResult({ error: '诊断请求失败。' });
        } finally {
            setToolStatus('');
        }
    };

    const viewSongDetails = async (songId) => {
        const song = allSongs.find(s => s.id === songId);
        if (!song) return;
        setSelectedSong({ isLoading: true, name: song.name, artist: song.artist, id: song.id });
        try {
            const response = await fetch(`/api/song_details/${songId}`);
            const data = await response.json();
            if (response.ok) {
                setSelectedSong(data);
            } else {
                setSelectedSong({ error: data.error, name: song.name, artist: song.artist, id: song.id });
            }
        } catch (err) {
            console.error(err);
            setSelectedSong({ error: '无法获取歌曲详情。', name: song.name, artist: song.artist, id: song.id });
        }
    };

    const handleViewComparison = (candidate) => {
        if (recognitionResult && recognitionResult.sample_analysis) {
            setComparisonDetails({
                sample: recognitionResult.sample_analysis,
                candidate: candidate
            });
        }
    };

    // --- Render Method ---
    return (
        <div className="space-y-8">
            <Header stats={stats} />
            <div className="flex space-x-2 border-b border-slate-700">
                <TabButton name="recognize" activeTab={activeTab} setActiveTab={setActiveTab}>单次识别</TabButton>
                <TabButton name="batch_test" activeTab={activeTab} setActiveTab={setActiveTab}>批量测试与调优</TabButton>
            </div>
            <main className="grid grid-cols-1 lg:grid-cols-3 gap-8">
                {activeTab === 'recognize' && (
                    <>
                        <div className="lg:col-span-1 space-y-8">
                            <Uploader onFileUpload={(file) => handleRecognize(file, null)} isLoading={isLoading && !recognitionResult} />
                            <SongList songs={filteredSongs} onViewDetails={viewSongDetails} searchQuery={songSearch} setSearchQuery={setSongSearch} />
                        </div>
                        <div className="lg:col-span-2 space-y-8">
                            <ResultPanel result={recognitionResult} error={error} isLoading={isLoading && !recognitionResult} onViewComparison={handleViewComparison} />
                            <TestSamplesCard samples={filteredSamples} onRecognize={handleRecognize} onDiagnose={handleDiagnose} searchQuery={sampleSearch} setSearchQuery={setSampleSearch} />
                        </div>
                    </>
                )}
                {activeTab === 'batch_test' && (
                    <>
                        <div className="lg:col-span-1 space-y-8">
                            <ToolsCard onGenerate={handleGenerateSamples} status={toolStatus} />
                            <ScoringConfigurator onRunTest={handleRunBatchTest} status={toolStatus} />
                        </div>
                        <div className="lg:col-span-2 space-y-8">
                            <BatchResultPanel result={latestBatchResult} onDiagnose={handleDiagnose} />
                            <BatchHistoryPanel />
                        </div>
                    </>
                )}
            </main>
            {selectedSong && <SongDetailModal song={selectedSong} onClose={() => setSelectedSong(null)} />}
            {diagnosisResult && <DiagnosisModal result={diagnosisResult} onClose={() => setDiagnosisResult(null)} />}
            {comparisonDetails && <MatchComparisonModal details={comparisonDetails} onClose={() => setComparisonDetails(null)} />}
        </div>
    );
};

// --- UI Sub-components ---
const Card = ({ children, className = '' }) => (<div className={`bg-slate-800 border border-slate-700 p-6 rounded-lg shadow-lg ${className}`}>{children}</div>);
const Button = ({ onClick, disabled, children, className = '', variant = 'primary' }) => { const baseClasses="w-full font-bold py-2 px-4 rounded-md transition-all duration-200 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-offset-slate-900 disabled:cursor-not-allowed disabled:opacity-60 border"; const variants={'primary':'bg-indigo-600 hover:bg-indigo-500 text-white border-indigo-700 focus:ring-indigo-500','secondary':'bg-slate-600 hover:bg-slate-500 text-slate-100 border-slate-500 focus:ring-slate-400','ghost':'bg-transparent hover:bg-slate-700 text-slate-300 border-slate-600 focus:ring-slate-500'}; return (<button onClick={onClick} disabled={disabled} className={`${baseClasses} ${variants[variant]} ${className}`}>{children}</button>);};
const TabButton = ({ name, activeTab, setActiveTab, children }) => { const isActive = name === activeTab; return (<button onClick={() => setActiveTab(name)} className={`px-4 py-2 text-sm font-medium transition-colors focus:outline-none rounded-t-md ${isActive ? 'border-b-2 border-indigo-500 text-white bg-slate-800' : 'text-slate-400 hover:text-white hover:bg-slate-800/50'}`}>{children}</button>);};
const Header = ({ stats }) => { return (<header className="flex justify-between items-center bg-slate-800/50 border border-slate-700 p-4 rounded-lg shadow-lg"><div className="flex items-center space-x-3"><Icon name="Music" size={28} className="text-indigo-400" /><h1 className="text-2xl font-bold text-white">音频检索与分析系统</h1></div><div className="text-right"><p className="text-sm text-slate-400">数据库曲目</p><p className="text-xl font-semibold text-white">{stats.track_count}</p></div></header>);};
const Uploader = ({ onFileUpload, isLoading }) => { const fileInputRef=useRef(null); const handleFileChange=(e)=>{const file=e.target.files[0]; if(file)onFileUpload(file)}; const handleDrop=(e)=>{e.preventDefault(); e.stopPropagation(); e.currentTarget.classList.remove('border-indigo-500','bg-slate-700/50'); const file=e.dataTransfer.files[0]; if(file)onFileUpload(file)}; const handleDragOver=(e)=>{e.preventDefault(); e.stopPropagation(); e.currentTarget.classList.add('border-indigo-500','bg-slate-700/50')}; const handleDragLeave=(e)=>{e.preventDefault(); e.stopPropagation(); e.currentTarget.classList.remove('border-indigo-500','bg-slate-700/50')}; return(<Card><h2 className="text-xl font-semibold mb-4 text-white">开始识别</h2><div className="border-2 border-dashed border-slate-600 rounded-lg p-8 text-center cursor-pointer transition-all duration-200" onClick={()=>fileInputRef.current.click()} onDrop={handleDrop} onDragOver={handleDragOver} onDragLeave={handleDragLeave}><input type="file" ref={fileInputRef} onChange={handleFileChange} className="hidden" accept="audio/*"/>{isLoading?(<div className="flex flex-col items-center justify-center h-20"><div className="loader mb-2"></div><p className="text-slate-400">正在处理中...</p></div>):(<div className="flex flex-col items-center justify-center h-20"><Icon name="UploadCloud" size={40} className="text-slate-500"/><p className="mt-2 text-slate-400">拖拽或<span className="text-indigo-400 font-semibold">点击上传</span></p></div>)}</div></Card>)};

// Merged SongList with search functionality
const SongList = ({ songs, onViewDetails, searchQuery, setSearchQuery }) => {
    const [currentPage, setCurrentPage] = useState(1);
    const songsPerPage = 6;
    const totalPages = Math.ceil(songs.length / songsPerPage);
    const currentSongs = songs.slice((currentPage - 1) * songsPerPage, currentPage * songsPerPage);

    useEffect(() => { setCurrentPage(1); }, [songs]);

    return (
        <Card className="min-h-[400px] flex flex-col">
            <h2 className="text-xl font-semibold mb-4 text-white">数据库曲库</h2>
            <DebouncedInput value={searchQuery} onChange={setSearchQuery} placeholder="搜索歌曲名或艺术家..." />
            <div className="space-y-2 flex-grow mt-4">
                {currentSongs.length > 0 ? (currentSongs.map(song => (
                    <div key={song.id} className="flex justify-between items-center bg-slate-700/50 p-3 rounded-md hover:bg-slate-700 transition-colors">
                        <div className="min-w-0">
                            <p className="font-medium text-white truncate">{song.name}</p>
                            <p className="text-sm text-slate-400 truncate">{song.artist || '未知艺术家'}</p>
                        </div>
                        <button onClick={() => onViewDetails(song.id)} className="ml-4 p-2 text-slate-400 hover:text-white rounded-full hover:bg-slate-600 transition-colors" title="查看详情"><Icon name="BarChart2" size={18} /></button>
                    </div>
                ))) : (
                    <div className="text-center py-8 text-slate-500 flex flex-col items-center justify-center h-full">
                        <Icon name="SearchX" size={32} className="mb-2" />
                        <p>没有找到匹配的歌曲</p>
                    </div>
                )}
            </div>
            {totalPages > 1 && (
                <div className="flex justify-center items-center pt-4 border-t border-slate-700 mt-4">
                    <div className="flex items-center space-x-1 bg-slate-700 border border-slate-600 px-1 py-1 rounded-md">
                        <button onClick={() => setCurrentPage(prev => Math.max(prev - 1, 1))} disabled={currentPage === 1} className="p-1 text-slate-300 hover:text-white disabled:text-slate-500 disabled:cursor-not-allowed"><Icon name="ChevronLeft" size={20} /></button>
                        <span className="text-sm text-slate-300 w-12 text-center">{currentPage}/{totalPages}</span>
                        <button onClick={() => setCurrentPage(prev => Math.min(prev + 1, totalPages))} disabled={currentPage === totalPages} className="p-1 text-slate-300 hover:text-white disabled:text-slate-500 disabled:cursor-not-allowed"><Icon name="ChevronRight" size={20} /></button>
                    </div>
                </div>
            )}
        </Card>
    );
};

// Merged TestSamplesCard with search functionality
const TestSamplesCard = ({ samples, onRecognize, onDiagnose, searchQuery, setSearchQuery }) => {
    return (
        <Card>
            <h2 className="text-xl font-semibold mb-4 text-white">测试样本</h2>
            <DebouncedInput value={searchQuery} onChange={setSearchQuery} placeholder="搜索样本文件名..." />
            <div className="mt-4 max-h-96 overflow-y-auto space-y-2 pr-2">
                {samples.length > 0 ? (samples.map(sample => (
                    <div key={sample} className="flex justify-between items-center bg-slate-700/50 p-3 rounded-md">
                        <p className="font-mono text-sm flex-1 truncate pr-4 text-slate-300">{sample}</p>
                        <div className="flex space-x-2">
                            <button onClick={() => onRecognize(null, `data/test_data/${sample}`)} className="bg-indigo-600 hover:bg-indigo-500 border border-indigo-700 text-white px-3 py-1 rounded text-sm transition-colors">识别</button>
                            <button onClick={() => onDiagnose(sample)} className="bg-slate-600 hover:bg-slate-500 border border-slate-500 text-white px-3 py-1 rounded text-sm transition-colors">诊断</button>
                        </div>
                    </div>
                ))) : (
                    <p className="text-slate-500 text-center py-4">没有找到匹配的样本。</p>
                )}
            </div>
        </Card>
    );
};

const ResultPanel = ({ result, error, isLoading, onViewComparison }) => { if(isLoading)return null; if(error)return <Card className="bg-red-900/20 border-red-700 text-center"><p className="text-red-300">{error}</p></Card>; if(!result)return <Card className="text-center text-slate-500"><p>上传音频文件以查看识别结果</p></Card>; const sampleName=result.sample_analysis?.name; return(<div className="space-y-6">{sampleName&&(<div className="text-center text-sm text-slate-400 border-b border-slate-700 pb-2 mb-4">测试样本: <span className="font-mono text-slate-300">{sampleName}</span></div>)}{result.match?<MatchCard match={result.match}/>:<NoMatchCard result={result}/>}{result.top_candidates&&result.top_candidates.length>0&&<CandidateList candidates={result.top_candidates} onViewComparison={onViewComparison}/>}{result.sample_analysis&&<SampleAnalysis analysis={result.sample_analysis}/>}</div>)};
const MatchCard = ({ match }) => { return(<Card className="bg-emerald-900/20 border-emerald-700"><h2 className="text-2xl font-bold text-emerald-300 mb-4 flex items-center space-x-2"><Icon name="CheckCircle2"/><span>识别成功</span></h2><div className="flex items-center space-x-4"><div className="flex-shrink-0 bg-slate-700 w-16 h-16 rounded-md flex items-center justify-center"><Icon name="Music" className="w-8 h-8 text-slate-400"/></div><div><p className="text-xl font-semibold text-white">{match.song_info.name}</p><p className="text-slate-300">{match.song_info.artist||'未知艺术家'}</p></div></div><audio controls src={`/api/audio/${match.song_info.id}`} className="w-full mt-4 h-10"/><div className="mt-4 grid grid-cols-2 md:grid-cols-4 gap-4 text-center"><div className="bg-slate-800/50 p-3 rounded-md"><p className="text-sm text-slate-400">总置信度</p><p className="text-lg font-bold text-emerald-400">{match.confidence}%</p></div><div className="bg-slate-800/50 p-3 rounded-md"><p className="text-sm text-slate-400">时间偏移</p><p className="text-lg font-bold text-white">~{match.offset}s</p></div><div className="bg-slate-800/50 p-3 rounded-md"><p className="text-sm text-slate-400">总票数</p><p className="text-lg font-bold text-white">{match.metrics.votes}</p></div><div className="bg-slate-800/50 p-3 rounded-md"><p className="text-sm text-slate-400">票数得分</p><p className="text-lg font-bold text-white">{match.metrics.vote_score}</p></div></div></Card>)};

// Updated NoMatchCard from new.js
const NoMatchCard = ({ result }) => {
    let message = '数据库中没有找到与上传片段高度相似的歌曲。';
    if (result.rejected_match) {
        message = `一个高票匹配 (Song ID: ${result.rejected_match.song_id}, ${result.rejected_match.metrics.votes} votes) 因置信度不足被拒绝。`;
    } else if (result.rejection_reason) {
        message = `正确拒绝: ${result.rejection_reason}`;
    }
    return (
        <Card className="bg-amber-900/20 border-amber-700 text-center">
            <h2 className="text-2xl font-bold text-amber-300 flex items-center justify-center space-x-2">
                <Icon name="XCircle" />
                <span>未找到可信匹配</span>
            </h2>
            <p className="text-slate-300 mt-2">{message}</p>
        </Card>
    );
};

const CandidateList = ({ candidates, onViewComparison }) => { return(<Card><h3 className="text-xl font-semibold mb-4 text-white">Top 5 候选曲目</h3><div className="space-y-3">{candidates.map(c=>(<div key={c.song_id+c.offset} className="bg-slate-700/50 border border-slate-700 p-3 rounded-md"><div className="flex justify-between items-center"><div><p className="font-medium text-white">{c.song_name}</p><p className="text-sm text-slate-400">Song ID: {c.song_id} | Offset: ~{c.offset}s</p></div><div className="flex items-center space-x-3"><div className="text-right"><p className="font-semibold text-indigo-400">{c.votes}</p><p className="text-xs text-slate-400">票</p></div><button onClick={()=>onViewComparison(c)} disabled={!c.spectrogram_url} className="bg-slate-600 hover:bg-slate-500 border border-slate-500 text-white p-2 rounded-md transition-colors disabled:bg-slate-700 disabled:text-slate-500 disabled:cursor-not-allowed" title="对比匹配片段"><Icon name="Search" size={16}/></button></div></div><audio controls src={`/api/audio/${c.song_id}`} className="w-full mt-2 h-8"/></div>))}</div></Card>)};
const SampleAnalysis = ({ analysis }) => { return(<Card><h3 className="text-xl font-semibold mb-4 text-white">上传样本分析</h3>{analysis.name&&(<audio controls src={`/api/sample_audio/${analysis.name}`} className="w-full mb-4 h-8"/>)}<div className="flex space-x-8 text-slate-300"><p><span className="font-semibold text-white">{analysis.peaks.length}</span> 个峰值点</p><p><span className="font-semibold text-white">{analysis.fingerprints_count}</span> 个指纹</p></div><div className="mt-4 h-64 bg-slate-900/50 rounded-md p-2 border border-slate-700"><PeakChart peaks={analysis.peaks}/></div></Card>)};

// Merged ToolsCard with noise generation from new.js
const ToolsCard = ({ onGenerate, status }) => {
    const [addNoise, setAddNoise] = useState(false);
    const [snr, setSnr] = useState(20);

    return (
        <Card>
            <h2 className="text-xl font-semibold mb-4 text-white">通用工具</h2>
            <div className="space-y-4">
                <div className="space-y-3 p-3 bg-slate-900/50 rounded-md border border-slate-700">
                    <label className="flex items-center space-x-2 cursor-pointer">
                        <input type="checkbox" checked={addNoise} onChange={(e) => setAddNoise(e.target.checked)} className="form-checkbox h-4 w-4 text-indigo-500 bg-slate-700 border-slate-600 rounded focus:ring-indigo-500" />
                        <span className="text-slate-300">为样本添加噪声</span>
                    </label>
                    {addNoise && (
                        <div className="flex items-center space-x-3">
                            <label className="text-sm text-slate-400">SNR:</label>
                            <input type="range" min="0" max="30" value={snr} onChange={(e) => setSnr(e.target.value)} className="w-full h-2 bg-slate-700 rounded-lg appearance-none cursor-pointer" />
                            <span className="text-sm font-mono w-12 text-center text-indigo-400">{snr} dB</span>
                        </div>
                    )}
                </div>
                <Button onClick={() => onGenerate({ add_noise: addNoise, snr_db: snr })} disabled={!!status} variant="secondary">
                    <div className="flex items-center justify-center space-x-2">
                        <Icon name="ListPlus" size={18} />
                        <span>{status.includes('生成') ? status : '生成测试样本'}</span>
                    </div>
                </Button>
            </div>
        </Card>
    );
};

const ScoringConfigurator = ({ onRunTest, status }) => {  const [config,setConfig]=useState(null);const [error,setError]=useState(null);useEffect(()=>{fetch('/api/config/scoring').then(res=>{if(!res.ok)throw new Error('Network response was not ok');return res.json()}).then(data=>setConfig(data)).catch(err=>setError('无法加载评分配置。'))},[]);const handleParamChange=(path,value)=>{const keys=path.split('.');setConfig(prevConfig=>{const newConfig=JSON.parse(JSON.stringify(prevConfig));let current=newConfig;for(let i=0;i<keys.length-1;i++){current=current[keys[i]]}current[keys[keys.length-1]]=parseFloat(value);return newConfig})};const handleRun=()=>{onRunTest({scoring:config})};if(error)return <Card><p className="text-red-400">{error}</p></Card>;if(!config)return <Card><div className="flex justify-center items-center h-40"><div className="loader"></div></div></Card>;return(<Card><h2 className="text-xl font-semibold mb-4 text-white">参数调优</h2><div className="space-y-4"><ConfigSlider label="最小票数 (min_votes)" value={config.min_votes} min={1} max={20} step={1} path="min_votes" onChange={handleParamChange}/><ConfigSlider label="最小比值 (min_ratio)" value={config.min_ratio} min={1.0} max={5.0} step={0.1} path="min_ratio" onChange={handleParamChange}/><h3 className="text-lg font-semibold pt-4 border-t border-slate-700 text-slate-300">验证阈值</h3><ConfigSlider label="最小置信度 (min_confidence)" value={config.verification_thresholds.min_confidence} min={0} max={100} step={1} path="verification_thresholds.min_confidence" onChange={handleParamChange}/><ConfigSlider label="最小峰值占比 (min_peak_ratio)" value={config.verification_thresholds.min_peak_ratio} min={0.01} max={1.0} step={0.01} path="verification_thresholds.min_peak_ratio" onChange={handleParamChange}/><ConfigSlider label="最小主导性 (min_dominance)" value={config.verification_thresholds.min_dominance} min={1.0} max={10.0} step={0.1} path="verification_thresholds.min_dominance" onChange={handleParamChange}/><ConfigSlider label="最小覆盖率 (min_coverage)" value={config.verification_thresholds.min_coverage} min={0.01} max={0.5} step={0.01} path="verification_thresholds.min_coverage" onChange={handleParamChange}/><ConfigSlider label="最大标准差 (max_std_dev)" value={config.verification_thresholds.max_std_dev} min={0.1} max={5.0} step={0.1} path="verification_thresholds.max_std_dev" onChange={handleParamChange}/></div><div className="mt-6"><Button onClick={handleRun} disabled={!!status} variant="primary"><div className="flex items-center justify-center space-x-2"><Icon name="TestTube2" size={18}/><span>{status.includes('测试')?status:'使用此配置运行测试'}</span></div></Button></div></Card>)};
const ConfigSlider = ({ label, value, min, max, step, path, onChange }) => (<div><label className="block text-sm font-medium text-slate-400 flex justify-between"><span>{label}</span><span className="font-mono text-indigo-400">{value}</span></label><input type="range" min={min} max={max} step={step} value={value} onChange={(e)=>onChange(path,e.target.value)} className="w-full h-2 bg-slate-700 rounded-lg appearance-none cursor-pointer"/></div>);
const BatchHistoryPanel = () => { const [history,setHistory]=useState([]);const [selected,setSelected]=useState([]);const [comparisonData,setComparisonData]=useState(null);const [isLoading,setIsLoading]=useState(false);useEffect(()=>{fetch('/api/batch_history').then(res=>res.json()).then(data=>setHistory(data)).catch(console.error)},[]);const handleSelect=(filename)=>{setSelected(prev=>{const newSelection=[...prev];const index=newSelection.indexOf(filename);if(index>-1){newSelection.splice(index,1)}else{if(newSelection.length<2){newSelection.push(filename)}else{newSelection.shift();newSelection.push(filename)}}return newSelection})};const handleCompare=async()=>{if(selected.length!==2)return;setIsLoading(true);setComparisonData(null);try{const [res1,res2]=await Promise.all([fetch(`/api/batch_history/${selected[0]}`).then(r=>r.json()),fetch(`/api/batch_history/${selected[1]}`).then(r=>r.json()),]);setComparisonData({reportA:res1,reportB:res2})}catch(error){console.error("Failed to fetch comparison data",error)}finally{setIsLoading(false)}};return(<Card><h2 className="text-xl font-semibold mb-4 text-white">测试历史与对比</h2><div className="max-h-60 overflow-y-auto space-y-2 pr-2 border-b border-slate-700 pb-4 mb-4">{history.length>0?history.map(item=>(<div key={item.filename} onClick={()=>handleSelect(item.filename)} className={`flex justify-between items-center p-2 rounded-md cursor-pointer transition-colors ${selected.includes(item.filename)?'bg-indigo-600/30 border border-indigo-500':'bg-slate-700/50 hover:bg-slate-700'}`}><span className="font-mono text-sm text-slate-300">{item.display_time}</span>{selected.includes(item.filename)&&<Icon name="Check" size={16} className="text-indigo-400"/>}</div>)):<p className="text-slate-500 text-center py-4">暂无历史记录</p>}</div><Button onClick={handleCompare} disabled={selected.length!==2||isLoading} variant="secondary">{isLoading?'正在加载对比...':`对比选中的 ${selected.length}/2 个结果`}</Button>{comparisonData&&<ComparisonResultPanel data={comparisonData}/>}</Card>)};
const ComparisonResultPanel = ({ data }) => { const {reportA,reportB}=data;const summaryA=reportA.summary;const summaryB=reportB.summary;const renderMetric=(label,valueA,valueB)=>{const numA=parseFloat(valueA);const numB=parseFloat(valueB);let colorClass='text-white';let icon=null;if(numA>numB){colorClass='text-emerald-400';icon=<Icon name="TrendingUp" size={14} className="inline-block ml-1"/>}else if(numA<numB){colorClass='text-red-400';icon=<Icon name="TrendingDown" size={14} className="inline-block ml-1"/>}return(<div><p className="text-sm text-slate-400">{label}</p><p className={`text-lg font-bold ${colorClass}`}>{valueB}{icon}</p><p className="text-xs text-slate-500">对比: {valueA}</p></div>)};return(<div className="mt-6 border-t border-slate-700 pt-4"><h3 className="text-lg font-semibold mb-3 text-center text-white">结果对比</h3><div className="grid grid-cols-2 md:grid-cols-3 gap-4 text-center">{renderMetric("总准确率",summaryA.overall_accuracy_score,summaryB.overall_accuracy_score)}{renderMetric("正确匹配",summaryA.correct_matches,summaryB.correct_matches)}{renderMetric("错误歌曲",summaryA.wrong_song_matches,summaryB.wrong_song_matches)}{renderMetric("未找到",summaryA.no_matches_found,summaryB.no_matches_found)}{renderMetric("处理错误",summaryA.processing_errors,summaryB.processing_errors)}{renderMetric("测试耗时(s)",summaryA.test_duration_seconds,summaryB.test_duration_seconds)}</div><ConfigDisplay config={summaryB.config_used.scoring} title="新配置"/><ConfigDisplay config={summaryA.config_used.scoring} title="旧配置"/></div>)};

// Updated BatchResultPanel from new.js
const BatchResultPanel = ({ result, onDiagnose }) => {
    if (!result) return <Card className="text-center text-slate-500"><p>点击“运行批量测试”以开始。报告将在此处显示。</p></Card>;
    if (result.error) return <Card className="bg-red-900/20 border-red-700 text-center"><p className="text-red-300">{result.error}</p></Card>;

    const { summary, test_cases } = result;
    const errorCases = test_cases.filter(c => c.status !== 'Correct' && c.status !== 'Correct Rejection');

    return (
        <div className="space-y-6">
            <Card>
                <h2 className="text-2xl font-bold mb-4 text-white">最新测试总结</h2>
                <div className="grid grid-cols-2 md:grid-cols-4 gap-4 text-center">
                    <div className="bg-slate-700 p-3 rounded-md"><p className="text-sm text-slate-400">总准确率</p><p className="text-xl font-bold text-indigo-400">{summary.overall_accuracy_score}%</p></div>
                    <div className="bg-slate-700 p-3 rounded-md"><p className="text-sm text-slate-400">总样本</p><p className="text-xl font-bold text-white">{summary.total_samples}</p></div>
                    <div className="bg-emerald-800 p-3 rounded-md"><p className="text-sm text-slate-400">正确识别</p><p className="text-xl font-bold text-emerald-400">{summary.correct_matches}</p></div>
                    <div className="bg-emerald-900/50 p-3 rounded-md"><p className="text-sm text-slate-400">正确拒绝</p><p className="text-xl font-bold text-emerald-500">{summary.correct_rejections}</p></div>
                    <div className="bg-red-900/50 p-3 rounded-md"><p className="text-sm text-slate-400">错误歌曲</p><p className="text-xl font-bold text-red-400">{summary.wrong_song_matches}</p></div>
                    <div className="bg-amber-900/50 p-3 rounded-md"><p className="text-sm text-slate-400">未找到(FN)</p><p className="text-xl font-bold text-amber-400">{summary.false_negatives}</p></div>
                    <div className="bg-fuchsia-900/50 p-3 rounded-md"><p className="text-sm text-slate-400">错误识别(FP)</p><p className="text-xl font-bold text-fuchsia-400">{summary.false_positives}</p></div>
                    <div className="bg-slate-700 p-3 rounded-md"><p className="text-sm text-slate-400">测试耗时</p><p className="text-xl font-bold text-white">{summary.test_duration_seconds}s</p></div>
                </div>
                {summary.config_used?.scoring && <ConfigDisplay config={summary.config_used.scoring} title="本次测试使用的评分参数" />}
            </Card>
            {errorCases.length > 0 && (
                <Card>
                    <h3 className="text-xl font-semibold mb-4 text-red-400">失败与错误案例 ({errorCases.length})</h3>
                    <div className="space-y-4 max-h-96 overflow-y-auto pr-2">
                        {errorCases.map((c, index) => <ErrorCaseCard key={index} testCase={c} onDiagnose={onDiagnose} />)}
                    </div>
                </Card>
            )}
        </div>
    );
};

const ErrorCaseCard = ({ testCase, onDiagnose }) => { return(<div className="bg-slate-700/50 border border-slate-700 p-4 rounded-md text-xs"><div className="flex justify-between items-start"><div className="flex-grow min-w-0"><p className="font-bold truncate">样本: <span className="font-mono text-red-400">{testCase.sample}</span></p><p>真实歌曲: <span className="font-mono text-emerald-400">{testCase.ground_truth.name} at ~{testCase.ground_truth.offset}s</span></p><p>识别结果: {testCase.recognized?(<span className="font-mono text-amber-400">{testCase.recognized.name} at ~{testCase.recognized.offset}s</span>):(<span className="font-mono text-slate-400">{testCase.status}</span>)}</p></div><button onClick={()=>onDiagnose(testCase.sample)} className="bg-slate-600 hover:bg-slate-500 border border-slate-500 text-white px-3 py-1 rounded text-sm transition-colors ml-4 flex-shrink-0">诊断</button></div><audio controls src={`/api/sample_audio/${testCase.sample}`} className="w-full my-2 h-8"/>{testCase.recognized&&<audio controls src={`/api/audio/${testCase.recognized.song_id}`} className="w-full mt-2 h-8"/>}</div>)};
const ConfigDisplay = ({ config, title }) => { const [isOpen,setIsOpen]=useState(false);return(<div className="text-left text-xs mt-4 border-t border-slate-700 pt-3"><button onClick={()=>setIsOpen(!isOpen)} className="text-slate-400 hover:text-white flex items-center space-x-1 w-full text-left"><span>{title}</span><Icon name={isOpen?"ChevronUp":"ChevronDown"} size={14}/></button>{isOpen&&(<pre className="bg-slate-900/70 p-3 mt-2 rounded-md text-slate-300 overflow-x-auto">{JSON.stringify(config,null,2)}</pre>)}</div>)};

// --- Charting and Modal Components ---
const PeakChart = ({ peaks }) => { const chartRef=useRef(null); useEffect(()=>{if(!chartRef.current||!peaks)return; const chartInstance=new Chart(chartRef.current,{type:'scatter',data:{datasets:[{label:'峰值点',data:peaks.map(p=>({x:p[0],y:p[1]})),backgroundColor:'rgba(99, 102, 241, 0.7)',pointRadius:3,pointHoverRadius:5}]},options:{responsive:true,maintainAspectRatio:false,scales:{x:{type:'linear',position:'bottom',title:{display:true,text:'Time (s)',color:'#94a3b8'},ticks:{color:'#94a3b8'},grid:{color:'rgba(255, 255, 255, 0.1)'}},y:{title:{display:true,text:'Frequency (Hz)',color:'#94a3b8'},ticks:{color:'#94a3b8'},grid:{color:'rgba(255, 255, 255, 0.1)'}}},plugins:{legend:{display:false},tooltip:{callbacks:{label:(context)=>`Time: ${context.parsed.x.toFixed(2)}s, Freq: ${context.parsed.y.toFixed(0)}Hz`}}}}}); return()=>chartInstance.destroy()},[peaks]); return <canvas ref={chartRef}></canvas>};
const ComparisonChart = ({ original, sample }) => { const chartRef=useRef(null); useEffect(()=>{if(!chartRef.current||!original||!sample)return; const chartInstance=new Chart(chartRef.current,{type:'scatter',data:{datasets:[{label:`原曲: ${original.name}`,data:original.peaks.map(p=>({x:p[0],y:p[1]})),backgroundColor:'rgba(239, 68, 68, 0.6)',pointRadius:4,pointHoverRadius:6},{label:`样本: ${sample.name}`,data:sample.peaks.map(p=>({x:p[0],y:p[1]})),backgroundColor:'rgba(99, 102, 241, 0.8)',pointRadius:2,pointHoverRadius:4}]},options:{responsive:true,maintainAspectRatio:false,scales:{x:{type:'linear',position:'bottom',title:{display:true,text:'时间 (秒)',color:'#94a3b8'},ticks:{color:'#94a3b8'},grid:{color:'rgba(255, 255, 255, 0.1)'}},y:{title:{display:true,text:'频率 (Hz)',color:'#94a3b8'},ticks:{color:'#94a3b8'},grid:{color:'rgba(255, 255, 255, 0.1)'}}},plugins:{legend:{labels:{color:'#d1d5db'}},tooltip:{callbacks:{label:(context)=>`${context.dataset.label}: (${context.parsed.x.toFixed(2)}s, ${context.parsed.y.toFixed(0)}Hz)`}}}}}); return()=>chartInstance.destroy()},[original,sample]); return <canvas ref={chartRef}></canvas>};
const Modal = ({ children, onClose, title, size = '6xl' }) => { return(<div className="fixed inset-0 bg-black/80 backdrop-blur-sm flex items-center justify-center z-50 p-4" onClick={onClose}><div className={`bg-slate-800 w-full max-w-${size} max-h-[90vh] rounded-lg shadow-xl border border-slate-700 flex flex-col`} onClick={e=>e.stopPropagation()}><div className="flex justify-between items-center p-4 border-b border-slate-700 flex-shrink-0"><h2 className="text-xl font-bold text-white">{title}</h2><button onClick={onClose} className="text-slate-400 hover:text-white p-1 rounded-full hover:bg-slate-700"><Icon name="X"/></button></div><div className="overflow-y-auto p-6">{children}</div></div></div>)};
const SongDetailModal = ({ song, onClose }) => { const [view,setView]=useState('spectrogram'); useEffect(()=>{setView('spectrogram')},[song.id]); const renderContent=()=>{if(song.isLoading)return <div className="flex justify-center items-center h-96"><div className="loader"></div></div>; if(song.error)return <p className="text-red-400 text-center p-8">{song.error}</p>; const activeBtnClass="bg-indigo-600 text-white"; const inactiveBtnClass="bg-slate-700 hover:bg-slate-600 text-slate-300"; return(<div className="space-y-4"><div className="flex justify-center space-x-2 p-1 bg-slate-900 rounded-lg"><button onClick={()=>setView('spectrogram')} className={`px-4 py-2 rounded-md transition-colors text-sm font-medium ${view==='spectrogram'?activeBtnClass:inactiveBtnClass}`}>语谱图与峰值点</button><button onClick={()=>setView('constellation')} disabled={!song.constellation_url} className={`px-4 py-2 rounded-md transition-colors text-sm font-medium ${view==='constellation'?activeBtnClass:inactiveBtnClass} disabled:opacity-50 disabled:cursor-not-allowed`}>星座图</button></div><div className="w-full aspect-video bg-slate-900/50 rounded-md flex items-center justify-center overflow-hidden border border-slate-700">{view==='spectrogram'&&song.spectrogram_url&&<img src={`/plots/${song.spectrogram_url}`} alt="语谱图与峰值点" className="rounded-md w-full h-full object-contain"/>}{view==='constellation'&&song.constellation_url&&<img src={`/plots/${song.constellation_url}`} alt="星座图" className="rounded-md w-full h-full object-contain"/>}</div><p className="text-sm text-slate-400 text-center">共找到 {song.peak_count} 个峰值点。</p><audio controls src={`/api/audio/${song.id}`} className="w-full mt-4"/></div>)}; return(<Modal onClose={onClose} title={`${song.name} - ${song.artist||'未知艺术家'}`}>{renderContent()}</Modal>)};
const DiagnosisModal = ({ result, onClose }) => { const renderContent=()=>{if(result.isLoading)return <div className="flex justify-center items-center h-64"><div className="loader"></div><p className="ml-4 text-slate-400">正在诊断...</p></div>; if(result.error)return <p className="text-red-400 text-center p-8">{result.error}</p>; return(<div className="space-y-6 text-sm"><div><h3 className="font-bold text-lg mb-2 text-center text-white">语谱图与峰值点对比</h3><div className="grid grid-cols-1 md:grid-cols-2 gap-4">{result.original.spectrogram_url&&(<div className="bg-slate-900 p-2 rounded border border-slate-700"><p className="font-semibold text-slate-300 truncate text-center text-xs" title={result.original.name}>{result.original.name}</p><img src={`/plots/${result.original.spectrogram_url}`} alt="Original Spectrogram" className="rounded-md w-full"/></div>)}{result.sample.spectrogram_url&&(<div className="bg-slate-900 p-2 rounded border border-slate-700"><p className="font-semibold text-slate-300 truncate text-center text-xs" title={result.sample.name}>{result.sample.name}</p><img src={`/plots/${result.sample.spectrogram_url}`} alt="Sample Spectrogram" className="rounded-md w-full"/></div>)}</div></div><div className="grid grid-cols-1 md:grid-cols-2 gap-4"><div className="bg-slate-700 p-3 rounded"><p className="font-semibold text-slate-300 truncate" title={result.original.name}>原曲信息</p><p>{result.original.peaks.length} peaks, {result.original.fingerprints_count} fingerprints</p></div><div className="bg-slate-700 p-3 rounded"><p className="font-semibold text-slate-300 truncate" title={result.sample.name}>样本信息</p><p>{result.sample.peaks.length} peaks, {result.sample.fingerprints_count} fingerprints</p></div></div><div className="bg-slate-900 p-4 rounded-lg border border-slate-700"><h3 className="font-bold text-lg mb-2 text-center text-white">星座图对比</h3><div className="h-96"><ComparisonChart original={result.original} sample={result.sample}/></div></div><div className="bg-slate-900 p-4 rounded border border-slate-700"><h3 className="font-bold text-lg mb-2 text-white">对比结果</h3><p>匹配指纹数: <span className="font-mono text-lg text-indigo-400">{result.comparison.matches}</span></p><p>重合度: <span className="font-mono text-lg text-indigo-400">{result.comparison.overlap}%</span></p><p className="mt-2 text-slate-300">{result.comparison.analysis}</p></div></div>)}; return(<Modal onClose={onClose} title="诊断报告">{renderContent()}</Modal>)};
const MatchComparisonModal = ({ details, onClose }) => { if(!details)return null; const {sample,candidate}=details; return(<Modal onClose={onClose} title="匹配片段对比"><div className="space-y-4"><div className="grid grid-cols-1 md:grid-cols-2 gap-4"><div className="bg-slate-900 p-2 rounded border border-slate-700"><p className="font-semibold text-slate-300 truncate text-center text-xs">上传样本: <span className="font-mono">{sample.name}</span></p><div className="h-64 mt-2 bg-slate-900/50 rounded-md"><PeakChart peaks={sample.peaks}/></div></div><div className="bg-slate-900 p-2 rounded border border-slate-700"><p className="font-semibold text-slate-300 truncate text-center text-xs" title={candidate.song_name}>候选匹配: {candidate.song_name}</p>{candidate.spectrogram_url?(<img src={`/plots/${candidate.spectrogram_url}`} alt="Candidate Spectrogram" className="rounded-md w-full mt-2"/>):<p className="text-center text-slate-500 mt-4">无法生成此片段的可视化。</p>}</div></div><div className="bg-slate-700 p-3 rounded text-center"><p>候选曲目 <span className="font-bold text-indigo-400">{candidate.song_name}</span> 在约 <span className="font-bold text-indigo-400">{candidate.offset}s</span> 处的片段获得了 <span className="font-bold text-indigo-400">{candidate.votes}</span> 票。</p></div></div></Modal>)};

// --- Application Entry Point ---
const container = document.getElementById('root');
const root = createRoot(container);
root.render(<App />);
